//Processing.logger = console;
LogFile oLogFile;
WordNet oWordNet;
EdgeMap oEdgeMap;
ViewFinder oViewFinder;
LogView oLogView;

int iWidth = 1000;
int iHeight = 700;

String sFileLoaded;

void loadAll()
{
  println("Creating LogFile");
  sFileLoaded = sFileToLoadJS;
  oLogFile = new LogFile(sFileLoaded);
  println("Loading LogFile");
  oLogFile.load();
  
  println("Creating WordNet");
  oWordNet = new WordNet(oLogFile);
  println("Building WordNet");
  oWordNet.build();  
  
  println("Creating LogView");
  oLogView = new LogView(iWidth, iHeight, oLogFile);
  println("Creating EdgeMap");
  oEdgeMap = new EdgeMap(iWidth, iHeight, oWordNet, oLogFile);
  println("Creating ViewFinder");
  oViewFinder = new ViewFinder(iWidth, iHeight, oEdgeMap, oLogView);
}

void setup() 
{
  size(iWidth, iHeight);
  background(0);
  stroke(255);
  frameRate(12);
  loadAll();
}

void draw() 
{
  if (sFileLoaded != sFileToLoadJS)
  {
    loadAll();
  }

  background(0);
  oViewFinder.draw();
}

void mousePressed()
{
  if (mouseButton == LEFT)
  {
    if (oViewFinder.mode == ViewFinder.VIEW_MODE || oViewFinder.mode == ViewFinder.LOGVIEW_MODE)
      oViewFinder.sliderScrollPressed();
	else if (oViewFinder.mode == ViewFinder.PAGE_MODE || oViewFinder.mode == ViewFinder.LOGPAGE_MODE)
      oViewFinder.pageScrollPressed();
  }
  else if (mouseButton == RIGHT)
  {
    oViewFinder.pageScrollPressed();
  }
}

void mouseReleased()
{
  if (mouseButton == LEFT)
  {
    if (oViewFinder.mode == ViewFinder.VIEW_MODE || oViewFinder.mode == ViewFinder.LOGVIEW_MODE)
      oViewFinder.sliderScrollReleased();
	else if (oViewFinder.mode == ViewFinder.PAGE_MODE || oViewFinder.mode == ViewFinder.LOGPAGE_MODE)
      oViewFinder.pageScrollReleased();
  }
  else if (mouseButton == RIGHT)
  {
    oViewFinder.pageScrollReleased();
  }  
}

void keyTyped()
{
  switch(key)
  {
    case 'D': oLogView.shade(-10);
	          break;
    case 'L': oLogView.shade(10);
	          break;
    case 'l': oViewFinder.shade(-10);
	          break;
    case 'd': oViewFinder.shade(10);
	          break;
    // Modes
    case 'v': oViewFinder.mode = ViewFinder.VIEW_MODE;
	          break;
    case 'p': oViewFinder.mode = ViewFinder.PAGE_MODE;
	          break;
    case 'V': oViewFinder.mode = ViewFinder.LOGVIEW_MODE;
	          break;
    case 'P': oViewFinder.mode = ViewFinder.LOGPAGE_MODE;
	          break;
  }
  
  if (oLogView.iFillColor > 255)
    oLogView.iFillColor = 255;
  else if (oLogView.iFillColor < 0)
    oLogView.iFillColor = 0;
}

class LogFile 
{
  String sPath = "";
  String[] asLines;
  int iNumLines = 0;
  
  LogFile(String sPath) 
  {
    this.sPath = sPath;
  }
  
  void load() 
  {
    this.asLines = loadStrings(this.sPath);
    this.iNumLines = this.asLines.length;
	println("Number of lines loaded from "+sPath+" is "+iNumLines);
  }
  
  int length() 
  {
    return this.iNumLines; 
  }
  
  // Return range
  String[] get(int iFromIndex, int iToIndex) 
  {
    // Not loaded
    if (this.iNumLines == 0)
      return null;

    if (iFromIndex < 0)
      iFromIndex = 0;
	
	if (iToIndex > this.iNumLines)
      iToIndex = this.iNumLines;
    
    int i = 0;
    int iNumLinesToGet = iToIndex - iFromIndex;
    String[] asTempLines = new String[iNumLinesToGet];
    while (i < iNumLinesToGet)
    {
      asTempLines[i] = this.asLines[iFromIndex + i];
      i++;
    }
    return asTempLines;
  }
  
  // Return all
  String[] get() 
  {
    return this.get(0, this.length());
  }
  
  // Return a line
  String get(int iIndex) 
  {
    String[] sLine = this.get(iIndex, iIndex+1);
    if (sLine.length > 0)
      return sLine[0];
    else
      return null;
  }
}

class WordNet 
{
  LogFile oFile;
  HashMap hWords;
  String sSplitChars = " ";
  int iMaxNumEdges = 0;
  
  WordNet(LogFile oFile) 
  {
    this.oFile = oFile;
    this.hWords = new HashMap();
  }
  
  String[] splitWords(String sLine)
  {
     String[] asWords = splitTokens(sLine, this.sSplitChars);
	 return asWords;
  }
  
  // Build a HashMap of HashMaps of word pairs (aka edges)
  // and keep track of the count
  void build()
  {
    int iLineIndex = 0;
    int iLength = this.oFile.iNumLines;
    while (iLineIndex < iLength) 
    {
      String sLine = oFile.get(iLineIndex);
      if (sLine == null)
        break;
      
      String[] asWords = this.splitWords(sLine);
      int iNumWords = asWords.length;
      int iWordIndex = 0;
      while (iWordIndex < iNumWords - 1)
      {
        String sWordA = asWords[iWordIndex];
        String sWordB = asWords[iWordIndex + 1];
        iWordIndex++;
       
        if ((sWordA == "") || (sWordB == ""))   
          continue;
      
        HashMap hWordAList;
        if (! this.hWords.containsKey(sWordA))
          hWordAList = new HashMap();
        else
          hWordAList = (HashMap) this.hWords.get(sWordA);
        
        if (! hWordAList.containsKey(sWordB)) 
        {
          hWordAList.put(sWordB, (int) 1);
        }
        else 
        {
          Integer iCount = (Integer) hWordAList.get(sWordB);
          iCount++;
          if (iCount > this.iMaxNumEdges)
            this.iMaxNumEdges = iCount;
            
          hWordAList.put(sWordB, iCount);
        }
        this.hWords.put(sWordA, hWordAList);
      }
      iLineIndex++;
    }
  }
  
  HashMap getNet() 
  {
    return this.hWords;
  }
  
  int getMaxEdges() 
  {
    return this.iMaxNumEdges;
  }
}


class EdgeMap 
{
  int iWidth;
  int iHeight;
  int iNumPixPerChar;
  WordNet oWordNet;
  LogFile oLogFile;
  HashMap hEdgeWeights;
  PImage oImage;
  
  EdgeMap(int iWidth, int iHeight, WordNet oWordNet, LogFile oLogFile) 
  {
    this.iWidth = iWidth;
    this.iHeight = iHeight;
    this.oWordNet = oWordNet;
    this.oLogFile = oLogFile;
    
    this.iNumPixPerChar = 5;
    
    this.hEdgeWeights = new HashMap();
    this.calcEdgeWeights();
  }
  
  void generate(int iLineFrom, int iLineTo)
  {
    ArrayList aPixels = new ArrayList();
    
    int iLength = this.oLogFile.length();
    if (iLineTo > iLength)
      iLineTo = iLength;
    if (iLineFrom < 0)
      iLineFrom = 0;
    if (iLineFrom > iLength)
      return;
      
    String[] asLogLines = this.oLogFile.get(iLineFrom, iLineTo);
    iLength = asLogLines.length;
    /*if (iLineTo > iLength)
      iLineTo = iLength;
    if (iLineFrom < 0)
      iLineFrom = 0;
    if (iLineFrom > iLength)
      return;
	*/
    
    int iImageSizeFudge = 2; // This means we allow every line to be split once
    this.oImage = createImage(this.iWidth, asLogLines.length * iImageSizeFudge, RGB);
    this.oImage.loadPixels();

    int iNumLinesUsed = 0;
    //for (int iLineIndex = iLineFrom; iLineIndex < iLineTo; iLineIndex++)
    for (int iLineIndex = 0; iLineIndex < iLength; iLineIndex++)
    {
      String sLine = asLogLines[iLineIndex];
      String[] asWords = this.oWordNet.splitWords(sLine);  
        
      int iNumWords = asWords.length;
      int iWordIndex = 0;
      int iWordPixIndex = 0;
      int iNewWordPixIndex = 0;
      while (iWordIndex < iNumWords - 1)
      {
        String sWordA = asWords[iWordIndex];
        String sWordB = asWords[iWordIndex + 1];
        if ((sWordA == "") || (sWordB == ""))
        {
          iWordIndex++;
          continue;
        }
          
        float fWeight = 0;
        if (this.hEdgeWeights.containsKey(sWordA))
        {
          HashMap hWordA = (HashMap) this.hEdgeWeights.get(sWordA);
          if (hWordA.containsKey(sWordB))
            fWeight = (Float)((Map)this.hEdgeWeights.get(sWordA)).get(sWordB);
          else
            continue;
        }
        else if (this.hEdgeWeights.containsKey(sWordB))
        {
          HashMap hWordB = (HashMap) this.hEdgeWeights.get(sWordB);
          if (hWordB.containsKey(sWordA))
            fWeight = (Float)((Map)this.hEdgeWeights.get(sWordB)).get(sWordA);
          else
            continue;
        }
        else
        { 
          continue; 
        }
        colorMode(HSB);
        color c = color(fWeight * 255 / 3, 220, 220);
        color oEmptyColor = color(0);
        colorMode(RGB);
        
        int iWordPairLength = sWordA.length() + sWordB.length();
        int iNumPixForPair = this.iNumPixPerChar * iWordPairLength;
        iNewWordPixIndex = iWordPixIndex + iNumPixForPair;

        if (iNewWordPixIndex >= this.iWidth)
        {
          iWordPixIndex = 0;
          iNewWordPixIndex = iNumPixForPair;
          iNumLinesUsed++;
        }
        
        // Colour in the data
        for (int iPixIndex = iWordPixIndex; iPixIndex < iNewWordPixIndex; iPixIndex++)
        {
          int iImageIndex = (this.iWidth * iLineIndex) + iPixIndex;
          this.oImage.pixels[iImageIndex] = c;      
        }
          
        iWordPixIndex = iNewWordPixIndex;
        iWordIndex++;
      }
      iNumLinesUsed++;
    }
    this.oImage.updatePixels();
  }

  void calcEdgeWeights()
  {
    HashMap hWords = this.oWordNet.hWords;
    int iMaxNumEdges = this.oWordNet.iMaxNumEdges;
    
    Iterator i = hWords.entrySet().iterator();
    while (i.hasNext())
    {
      Map.Entry oWordA = (Map.Entry) i.next();
      String sWordA = (String) oWordA.getKey();
      HashMap hWordA = (HashMap) oWordA.getValue();  
      
      HashMap hWordAWeights = new HashMap();
      Iterator j = hWordA.entrySet().iterator();
      while (j.hasNext()) 
      {
        Map.Entry oWordB = (Map.Entry) j.next();
        String sWordB = (String) oWordB.getKey();
        int iEdgeCount = (Integer) oWordB.getValue();
          
        float fWeight = log(iEdgeCount) / log(iMaxNumEdges);

        hWordAWeights.put(sWordB, fWeight);
      }
      this.hEdgeWeights.put(sWordA, hWordAWeights);
    }   
  }
}

class ViewFinder
{
  static int VIEW_MODE = 0;
  static int PAGE_MODE = 1;
  static int LOGVIEW_MODE = 2;
  static int LOGPAGE_MODE = 3;

  int iWidth;
  int iHeight;
  
  boolean bPageScroll = false;
  boolean bSliderScroll = false;
  boolean bHidden = true;
  
  int mode;
  int iStartMouseY;
  
  int iLinePosFrom;
  int iLinePosTo;
  
  //int iCurrentLinesFrom;
  //int iCurrentLinesTo;
  int iCurrentPosition;
  int iPageY;
  int iPageNumber = 1;
  
  //int iPageLineFrom;
  //int iPageLineTo;
  int iPageHeight;
  int iSliderLineFrom;
  //int iSliderLineTo;
  int iSliderHeight = 50;
  int iFillColor = 220;
  int iGenerate = 350;
  int iPageCount = 2;
  
  EdgeMap oEdgeMap;  
  LogView oLogView;
  
  ViewFinder(int iWidth, int iHeight, EdgeMap oEdgeMap, LogView oLogView)
  {
    this.iWidth = iWidth;
    this.iHeight = iHeight;
    this.oEdgeMap = oEdgeMap;
    this.oLogView = oLogView;
	mode = VIEW_MODE;
    
    this.iPageHeight = 3 * this.iHeight;
    
    //this.iPageLineFrom = 0;
    //this.iPageHeight = 3 * this.iHeight;
    
    this.iSliderLineFrom = 0;
    //this.iSliderLineTo = this.iSliderHeight;
    
    //this.iLinePosFrom = 0;
    //this.iLinePosTo = this.iPageHeight;
    
    this.generate(0, this.iPageHeight);
  }
 
  void draw()
  { 
    
    if (bSliderScroll)
    {
      int iDiffY = mouseY - this.iStartMouseY;
      this.iStartMouseY = mouseY;
      this.iSliderLineFrom += iDiffY;
      if (this.iSliderLineFrom < 0)
        this.iSliderLineFrom = 0;
      //this.iSliderLineTo += iDiffY;
    }
    else if (bPageScroll)
    {
      int iDiffY = mouseY - this.iStartMouseY;
      this.iStartMouseY = mouseY;
      
      /*this.iPageLineFrom += iDiffY;
      if (this.iPageLineFrom > 0)
        this.iPageLineFrom = 0;
      this.iPageLineTo = this.iPageLineFrom + this.iPageHeight;
      //this.iPageLineTo += iDiffY;      
      */
      this.iPageY += iDiffY;

      boolean bGoingDown = true;
      if (iDiffY > 0)
        bGoingDown = false;
        
      // Anchor the page to its boundaries
      if (this.iPageY > 0)
      { 
        // Scrolling up
        //this.iPageY = this.iCurrentPosition;
        this.iPageY = 0;
        iDiffY = 0;
      }
      else if (this.iPageY < -1 * (this.iPageHeight - this.iHeight))
      {
        // Scrolling down
        this.iPageY = -1 * (this.iPageHeight - this.iHeight);
        iDiffY = 0;
      }
      this.iCurrentPosition -= iDiffY;

      if ((this.iPageY == 0) && (! bGoingDown))
      {
        // Top of the page and scrolling upwards
        println("Going up...");
        //this.iCurrentPosition = (this.iPageNumber * this.iPageHeight);        
        this.iCurrentPosition = ((this.iPageNumber - 1) * this.iPageHeight) - this.iHeight;
        if (this.iCurrentPosition < 0)
          this.iCurrentPosition = 0;
      
        int iFrom = this.iCurrentPosition - (this.iPageHeight - this.iHeight);
        if (iFrom < 0)
          iFrom = 0;
        int iTo = this.iCurrentPosition + this.iHeight;
        
        if ((iFrom != this.iLinePosFrom) && (iTo != this.iLinePosTo))
        {
          this.generate(iFrom, iTo);
          this.iPageY = -1 * (this.iPageHeight - this.iHeight);
          this.iPageNumber--;         
        }
      }
      else if ((this.iPageY == -1 * (this.iPageHeight - this.iHeight)) && (bGoingDown))
      {
        // Bottom of page and scrolling downwards
        println("Going down..");
        this.iCurrentPosition = (this.iPageNumber * this.iPageHeight) - this.iHeight; 
        
        int iMaxPosition = this.oEdgeMap.oLogFile.length() - this.iHeight;
        if (this.iCurrentPosition >  iMaxPosition)
          this.iCurrentPosition = iMaxPosition;
          
        int iFrom = this.iCurrentPosition;
        int iTo = this.iCurrentPosition + this.iPageHeight;
        if ((iFrom != this.iLinePosFrom) && (iTo != this.iLinePosTo))
        {
          this.generate(iFrom, iTo);
          this.iPageY = 0;               
          this.iPageNumber++;
        }
      }
    }
    
    //int iLogViewFrom = this.iCurrentLinesFrom + this.iSliderLineFrom;
    int iLogViewFrom = this.iCurrentPosition + this.iSliderLineFrom;
    int iLogViewTo = iLogViewFrom + this.iSliderHeight;
        
    // Draw the EdgeMap
    if (this.oEdgeMap.oImage != null)
    {
      image(this.oEdgeMap.oImage, 0, this.iPageY);
    }
    
    // Draw the slider
    noStroke();
    fill(0,100);
    rect(0,0,this.iWidth, this.iSliderLineFrom);
    rect(0,iSliderLineFrom + this.iSliderHeight, this.iWidth, this.iHeight - this.iSliderLineFrom - this.iSliderHeight);
    
    // Draw the mask
    if (this.bHidden)
    {
      fill(0,this.iFillColor);
      rect(0, 0, this.iWidth, this.iHeight);
    }
    
    // Draw the logview
    this.oLogView.draw(iLogViewFrom, iLogViewTo);
  }
  
  void generate(int iFrom, int iTo)
  {
    //int iLinePosTo = this.iLinePosTo + 2 * this.iHeight;
    println("generating from "+iFrom+" to "+iTo);
    this.oEdgeMap.generate(iFrom, iTo); 
	println("done");
  }

  void sliderScrollPressed()
  {
    this.bSliderScroll = true;
    this.iStartMouseY = mouseY;
    if ((keyPressed == true && keyCode ==  CTRL) || mode == ViewFinder.LOGVIEW_MODE)
    {
      this.bHidden = true;
    }
    else
    {
      this.oLogView.hide();
      this.bHidden = false;
    }
  }
  
  void sliderScrollReleased()
  {
    this.bSliderScroll = false;
    this.oLogView.show();
    this.bHidden = true;

  }
  
  void pageScrollPressed()
  {
    this.bPageScroll = true;
    this.iStartMouseY = mouseY;
    if ((keyPressed == true && keyCode == CTRL) || mode == ViewFinder.LOGPAGE_MODE)
    {
      this.bHidden = true;
    }
    else
    {
      this.oLogView.hide();
      this.bHidden = false;
    }
  }
  
  void pageScrollReleased()
  {
    this.bPageScroll = false;
    this.oLogView.show();
    this.bHidden = true;    
  }
  
  void shade(int iDiff)
  {
    this.iFillColor += iDiff;
    if (this.iFillColor > 255)
      this.iFillColor = 255;
    else if (this.iFillColor < 0)
      this.iFillColor = 0; 
  }  
}

class LogView
{
  int iWidth;
  int iHeight;
  LogFile oLogFile;
  PFont font;
  boolean bHidden = false;
  int iFillColor = 200;
 
  LogView(int iWidth, int iHeight, LogFile oLogFile)
  {
    this.iWidth = iWidth;
    this.iHeight = iHeight;
    this.oLogFile = oLogFile;
    
    //this.font = loadFont("FreeMono-14.vlw");
	this.font = createFont("FreeMono", 14);
	textFont(this.font);
  } 
  
  void shade(int iDiff)
  {
    this.iFillColor += iDiff;
    if (this.iFillColor > 255)
      this.iFillColor = 255;
    else if (this.iFillColor < 0)
      this.iFillColor = 0; 
  }
  
  void hide()
  {
    this.bHidden = true;
  }
  
  void show()
  {
    this.bHidden = false;
  }
  
  void draw(int iLineFrom, int iLineTo)
  {
    if (iLineFrom < 0)
      iLineFrom = 0;
    
    if (iLineTo > this.oLogFile.length())
      iLineTo = this.oLogFile.length();
      
    textFont(this.font);
    noStroke();
    
    if (this.bHidden)
      fill(this.iFillColor,50);
    else
      fill(this.iFillColor,255);
    
    String[] asLines = this.oLogFile.get(iLineFrom, iLineTo);
    if (asLines == null)
      return;
    int iNumLines = asLines.length;
    
    for (int i=0; i<iNumLines; i++)
    {
      int iLineNum = iLineFrom+i;
      text("["+iLineNum+"] "+asLines[i], 0, i*14);
    }
  }
}

